package nl.utwente.viskell.ui.components;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import nl.utwente.viskell.ghcj.GhciSession;
import nl.utwente.viskell.haskell.expr.Expression;
import nl.utwente.viskell.haskell.type.FunType;
import nl.utwente.viskell.haskell.type.Type;
import nl.utwente.viskell.haskell.type.TypeScope;
import nl.utwente.viskell.ui.ToplevelPane;
import java.util.*;
import java.util.concurrent.ExecutionException;
/**
* Block that accepts a (Float -> Float) function to be displayed on a linechart
* inside the Block.
*/
public class GraphBlock extends Block {
/** The InputAnchor of this Block. */
private InputAnchor input;
/** The Pane that contains the inputs. */
@FXML private Pane inputSpace;
/** The LineChart that is displayed inside this Block. */
@FXML private LineChart<Double, Double> chart;
/** NumberAxis for x. */
@FXML private NumberAxis x;
/** NuberAxis for y. */
@FXML private NumberAxis y;
/**
* Constructs a new GraphBlock.
* @param pane The CustomUIPane on which this Block resides.
*/
public GraphBlock(ToplevelPane pane) {
super(pane);
loadFXML("GraphBlock");
input = new InputAnchor(this);
input.layoutXProperty().bind(inputSpace.widthProperty().divide(2));
inputSpace.getChildren().setAll(input);
//Make sure inputSpace is drawn on top.
BorderPane borderPane = (BorderPane) inputSpace.getParent();
borderPane.getChildren().remove(inputSpace);
borderPane.setTop(inputSpace);
}
@SuppressWarnings("UnusedParameters")
public static GraphBlock fromBundleFragment(ToplevelPane pane, Map<String, Object> bundleFragment) {
return new GraphBlock(pane);
}
@Override
public List<InputAnchor> getAllInputs() {
return ImmutableList.of(input);
}
@Override
public List<OutputAnchor> getAllOutputs() {
return ImmutableList.of();
}
@Override
public Optional<Block> getNewCopy() {
return Optional.of(new GraphBlock(this.getToplevel()));
}
@Override
public Expression getLocalExpr(Set<OutputAnchor> outsideAnchors) {
return input.getLocalExpr(outsideAnchors);
}
@Override
public void refreshAnchorTypes() {
this.input.setFreshRequiredType(new FunType(Type.con("Double"), Type.con("Double")), new TypeScope());
}
@Override
public void invalidateVisualState() {
this.input.invalidateVisualState();
if (! (this.inValidContext && this.input.hasValidConnection())) {
return;
}
ObservableList<XYChart.Series<Double, Double>> lineChartData = FXCollections.observableArrayList();
double step = 0.01;
double min = x.getLowerBound();
double max = x.getUpperBound();
try {
GhciSession ghciSession = getToplevel().getGhciSession();
String funName = "graph_fun_" + Integer.toHexString(this.hashCode());
ghciSession.push(funName, this.getAllInputs().get(0).getFullExpr());
String range = String.format(Locale.US, " [%f,%f..%f]", min, min+step, max);
String results = ghciSession.pullRaw("putStrLn $ unwords $ map show $ map " + funName + range).get();
LineChart.Series<Double, Double> series = new LineChart.Series<>();
ObservableList<XYChart.Data<Double, Double>> data = series.getData();
Iterator<String> v = Splitter.on(' ').split(results).iterator();
for (double i = min; i < max; i += step) {
data.add(new XYChart.Data<>(i, Double.valueOf(v.next())));
}
lineChartData.add(series);
} catch (NoSuchElementException | NumberFormatException | InterruptedException | ExecutionException ignored) {
// Pretend we didn't hear anything.
}
chart.setData(lineChartData);
}
@Override
public boolean checkValidInCurrentContainer() {
if (this.container instanceof LambdaContainer || this.container instanceof Lane) {
return false;
} else {
return super.checkValidInCurrentContainer();
}
}
}